﻿#region License
/* Copyright (C) 2009 Tim Coulter
 * 
 * This file is part of ExtremeML.
 * 
 * ExtremeML is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation either version
 * 2 of the License, or (at your option) any later version.
 * 
 * ExtremeML is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
 * the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;

namespace ExtremeML.Builder
{
	/// <summary>
	/// A singleton class that provides metadata for the automated code generation of wrapper classes.
	/// </summary>
	public class Reflector
	{
		private static readonly Reflector ReflectorInstance = new Reflector();
	
		private Dictionary<Type, PartWrapperModel> _parts; //=9
		private Dictionary<Type, PartWrapperCollectionModel> _partCollections; //=4
		private Dictionary<Type, ElementWrapperModel> _elements; //=191 -> 239 -> 298
		private Dictionary<Type, ElementWrapperCollectionModel> _elementCollections; //=35 -> 54 - 70

		/// <summary>
		/// Initializes a new instance of the <see cref="Reflector"/> class.
		/// </summary>
		private Reflector()
		{
			Populate();
		}

		/// <summary>
		/// Gets the complete set of <see cref="PartWrapperModel"/> for the target class hierarchy.
		/// </summary>
		public IEnumerable<PartWrapperModel> Parts
		{
			get { return _parts.Values; }
		}

		/// <summary>
		/// Gets the complete set of <see cref="PartWrapperCollectionModel"/> for the target class hierarchy.
		/// </summary>
		public IEnumerable<PartWrapperCollectionModel> PartCollections
		{
			get { return _partCollections.Values; }
		}

		/// <summary>
		/// Gets the complete set of <see cref="ElementWrapperModel"/> for the target class hierarchy.
		/// </summary>
		public IEnumerable<ElementWrapperModel> Elements
		{
			get { return _elements.Values; }
		}

		/// <summary>
		/// Gets the complete set of <see cref="ElementWrapperCollectionModel"/> for the target class hierarchy.
		/// </summary>
		public IEnumerable<ElementWrapperCollectionModel> ElementCollections
		{
			get { return _elementCollections.Values; }
		}

		/// <summary>
		/// Gets the singleton instance of <see cref="Reflector"/>.
		/// </summary>
		public static Reflector Instance
		{
			get { return ReflectorInstance; }
		}

		/// <summary>
		/// Populates the singleton instance according to the configured root class.
		/// </summary>
		private void Populate()
		{
			_parts = new Dictionary<Type, PartWrapperModel>();
			_elements = new Dictionary<Type, ElementWrapperModel>();
			_partCollections = new Dictionary<Type, PartWrapperCollectionModel>();
			_elementCollections = new Dictionary<Type, ElementWrapperCollectionModel>();

			AddPackagePartsRecursive(typeof(SpreadsheetDocument));
			AddElementModelsRecursive();
			AddElementCollectionModelsRecursive();
		}

		/// <summary>
		/// Recursively reflects over the properties of the passed <see cref="Type"/> recording those
		/// types that derive from <see cref="OpenXmlPart"/> or <see cref="IEnumerable{OpenXmlPart}"/>.
		/// </summary>
		private void AddPackagePartsRecursive(
			Type type)
		{
			foreach (var property in type.GetProperties())
			{
				var inner = property.PropertyType;

				if (Lookup.IsPartType(inner))
				{
					// Record in _parts and reflect recursively
					if (!_parts.ContainsKey(inner) && !Lookup.IsUnsupportedType(inner))
					{
						_parts.Add(inner, new PartWrapperModel(
							new WrapperTypeInfo(inner, false), // example: ThemePartWrapper
							false)); // not part of a collection
						AddPackagePartsRecursive(inner);
					}
				}
				else if (Lookup.IsPartCollectionType(inner))
				{
					// Record in _parts and _partCollections and reflect recursively
					inner = Lookup.GetGenericContainedType(inner);

					if (!_parts.ContainsKey(inner) && !Lookup.IsUnsupportedType(inner))
					{
						var collection = new PartWrapperCollectionModel(
							type, //example: WorkbookPart
							new WrapperTypeInfo(inner, true), //example: WorksheetPartWrapperCollection
							new WrapperTypeInfo(inner, false)); //example: WorksheetPartWrapper

						_parts.Add(inner, new PartWrapperModel(
							new WrapperTypeInfo(inner, false), // example: WorksheetPartWrapper
							true)); // part of a collection
						_partCollections.Add(inner, collection);
						AddPackagePartsRecursive(inner);
					}
				}
			}
		}

		/// <summary>
		/// Recursively reflects over the properties of the identified package parts
		/// recording those types that derive from <see cref="OpenXmlElement"/>.
		/// </summary>
		private void AddElementModelsRecursive()
		{
			foreach (var type in _parts.Keys)
			{
				// Reflect over properties of the part type
				foreach (var property in type.GetProperties())
				{
					var inner = property.PropertyType;

					if (Lookup.IsElementType(inner))
					{
						// Record in _elements and reflect recursively
						if (!_elements.ContainsKey(inner) && !Lookup.IsUnsupportedType(inner))
						{
							_elements.Add(inner, new ElementWrapperModel(new WrapperTypeInfo(inner, false)));
							AddElementModelsRecursive(inner);
						}
					}
				}
			}
		}

		/// <summary>
		/// Identifies the set of <see cref="OpenXmlElement"/> child elements used by the passed <see cref="Type"/> 
		/// </summary>
		/// <remarks>
		/// This method represents a shortcut approach to discovering child elements, using the element
		/// reference table constructed by surveying help topics. Unlike the fully recursive discovery
		/// technique (described below), this implementation only yields 330 child elements when
		/// reflection is launched from a root type of <see cref="SpreadsheetDocument"/>. 
		/// </remarks>
		private void AddElementModelsRecursive(
			Type type)
		{
			foreach (var inner in Lookup.GetChildElementPropertyTypes(type).Keys)
			{
				// Record in _elements and reflect recursively
				if (!_elements.ContainsKey(inner) && !Lookup.IsUnsupportedType(inner))
				{
					_elements.Add(inner, new ElementWrapperModel(new WrapperTypeInfo(inner, false)));
					AddElementModelsRecursive(inner);
				}
			}

			foreach (var inner in Lookup.GetChildElementCollectionTypes(type))
			{
				// Record in _elements and reflect recursively
				if (!_elements.ContainsKey(inner) && !Lookup.IsUnsupportedType(inner))
				{
					_elements.Add(inner, new ElementWrapperModel(new WrapperTypeInfo(inner, false)));
					AddElementModelsRecursive(inner);
				}
			}
		}

		/// <summary>
		/// Returns a <see cref="Dictionary{Type, ElementWrapperCollectionModel}"/>
		/// representing the models required to support the designated collection
		/// properties of the element wrappers in the target class hierarchy.
		/// </summary>
		private void AddElementCollectionModelsRecursive()
		{
			foreach (var element in _elements)
			{
				var types = from property in element.Value.Properties
							where property.Category == PropertyCategory.ElementWrapperCollection
							select property.MappedType;

				foreach (var type in types)
				{
					if (!_elementCollections.ContainsKey(type))
					{
						_elementCollections.Add(type, new ElementWrapperCollectionModel(
							element.Key,
							new WrapperTypeInfo(type, true),
							new WrapperTypeInfo(type, false)));
					}
				}
			}
		}
	}
}
